package machine;

import java.util.ArrayList;
import static machine.RegMne.*;

public class Sam implements PrimitiveFunctions, InstructionFunctions { 
	static final int DATA_MEMORY_SIZE = 1024;
	
	Instructions codeSector;
	Pila stack = new Pila();

	public Sam(Instructions i) {
		codeSector = i;
		System.out.println("=> LOADING INSTRUCTIONS\n" + codeSector);
	}
	
	public void compile() {
		for (int i = 0; i < codeSector.size(); i++) {
			try {
				String str = "=> (" + CP.value + ")" + codeSector.get(CP.value);
				
				callInstruction(codeSector.get(CP.value));

				str +=	"\n\tStack=" + stack 
					+ RegMne.toStringAll();
				System.out.println(str);
			} catch (IndexOutOfBoundsException e) {
				System.err.println("===> codeSector overloaded!!!!!!!!! => " + CP.value
									 + "\n" + codeSector.size()
				);
			}
		}
	}
	
	private void callInstruction(Instruction inst) {
		switch (inst.op) {
			case LOAD:
				load(inst);
				break;
			case LOADA:
				loada(inst);
				break;
			case LOADI:
				loadi(inst);
				break;
			case LOADL:
				loadl(inst);
				break;
			case STORE:
				store(inst);
				break;
			case STOREI:
				storei(inst);
				break;
			case CALL:
				call(inst);
				break;
			case CALLI:
				calli(inst);
				break;
			case RETURN:
				return_(inst);
				break;
			case PUSH:
				push(inst);
				break;
			case POP:
				pop(inst);
				break;
			case JUMP:
				jump(inst);
				break;
			case JUMPI:
				jumpi(inst);
				break;
			case JUMPIF:
				jumpif(inst);
				break;
			case HALT:
				halt();
				break;
		}
	}
	
	int callPrimitive(Primitive prim) {
		switch(prim) {
			case ID:
				return id();
			case NOT:
				return not();
			case AND:
				return and();
			case OR:
				return or();
			case INC:
				return inc();
			case DEC:
				return dec();
			case NEG:
				return neg();
			case ADD:
				return add();
			case SUB:
				return sub();
			case MULT:
				return mult();
			case DIV:
				return div();
			case MOD:
				return mod();
			case LT:
				return lt();
			case LE:
				return le();
			case GT:
				return gt();
			case GE:
				return ge();
			case EQ:
				return eq();
			case NE:
				return ne();
			case TO21:
				return to21();
			case TO22:
				return to22();
			case NEW:
				return new_();
			case FREE:
				return free();
			case GET:
				return get();
			case PUT:
				return put();	
			default:
				return -1;
					
		}
	}
	
	/* ###########################################################
	 *  Instruction Functions
	 * ###########################################################
	 */
	
	@Override
	public void load(Instruction inst) {
		int dir = ((RegMne)inst.r).value + (Integer)inst.d;
		ArrayList<Integer> aux = stack.copyWords(dir, (Integer)inst.n);
		stack.add(ST.value, aux);
		CP.value++;
		ST.value++;
	}	
	
	@Override
	public void loada(Instruction inst) {	
		if ((SB.equals(inst.r))
			|| (ST.equals(inst.r))
			|| (LB.equals(inst.r))) 
		{
			stack.add(((RegMne)inst.r).value + (Integer)inst.d);
			ST.value++;
			CP.value++;
		}
	}
	
	@Override
	public void loadi(Instruction inst) {
	}
	
	@Override
	public void loadl(Instruction inst) {
		for (int i = 0; i < (Integer)inst.d - 1; i++) {
			stack.add(0);
			ST.value++;
		}
		stack.add((Integer)inst.n);
		ST.value++;
		CP.value++;
	}
	
	@Override
	public void store(Instruction inst) {

		int dir = ST.value - (Integer)inst.n;
		//System.out.println("-> " + dir + " ");

		ArrayList<Integer> aux = stack.copyWords(dir, (Integer)inst.n);
		//System.out.println(aux);
		for (int i = 0; i < (Integer)inst.n; i++) {
			stack.remove(stack.size() - 1);
		}
		
		//System.out.println(stack);

		int dir2 = ((RegMne)inst.r).value + (Integer)inst.d;
		
		//System.out.println("dir2: "+ dir2);
		//System.out.println("ST: " + ST.value);
		stack.add(dir2, aux);
		//System.out.println("Stack :" +stack);
		
		for (int i = 0; i < (Integer)inst.n; i++) {
			stack.remove(dir2 + (Integer)inst.n);
			ST.value--;
		}

		CP.value++;
	}
	
	@Override
	public void storei(Instruction inst) {}
	
	@Override
	public void call(Instruction inst) {
		if (inst.r.equals(PB)) {
			// CALL PB numero primitiva
			int v = callPrimitive((Primitive)inst.d);
			if (v != -1) {
				int i = stack.get(ST.value - 1);
				stack.set(i, v);
			}
			CP.value++;
		} else {
			stack.add(LB.value);
			stack.add(++CP.value);
			ST.value += 2;
			LB.value = ST.value;
			CP.value = (Integer)inst.d;			
		}
	}
	
	@Override
	public void calli(Instruction inst) {}
	
	@Override
	public void return_(Instruction inst) {}
	
	@Override
	public void push(Instruction inst) {
		int num_palabras = 0;
		if (inst.n != null) {
			num_palabras =  (Integer)(inst.n);
		}
		for (int i = 0; i < num_palabras; i++) {
			stack.add(0);
		}
		ST.value += num_palabras;
		CP.value++;		
	}
	
	@Override
	public void pop(Instruction inst) {
		ST.value = (Integer)inst.n + (Integer)inst.d;
		CP.value++;
	}
	
	@Override
	public void jump(Instruction inst) {}
	
	@Override
	public void jumpi(Instruction inst) {}
	
	@Override
	public void jumpif(Instruction inst) {}
	
	@Override
	public void halt() {
		System.exit(0);
	}
	
	/* ###########################################################
	 *  Primitive Functions
	 * ###########################################################
	 */
	@Override
	public int id() {
		int x = stack.get(ST.value - 1);
		stack.add(x);
		ST.value++;
		return -1;
	}
	
	@Override
	public int not() {
		int x = stack.get(ST.value - 1);
		stack.add(~x);
		ST.value--;
		return -1;
	}
	
	@Override
	public int and() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((Integer)(a & b));
		ST.value++;
		return -1;
	}
	
	@Override
	public int or() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((Integer)(a | b));
		ST.value++;
		return -1;
	}
	
	@Override
	public int inc() {
		int aux = stack.get(ST.value - 1) + 1;
		stack.set(ST.value - 1, aux);
		return -1;
	}
	
	@Override
	public int dec() {
		int aux = stack.get(ST.value - 1) - 1;
		stack.set(ST.value - 1, aux);
		return -1;
	}
	
	@Override
	public int neg() {
		int aux = stack.get(ST.value - 1) * (-1);
		stack.set(ST.value - 1, aux);
		return -1;
	}
	
	@Override
	public int add() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add(a + b);
		ST.value++;
		return -1;
	}
	
	@Override
	public int sub() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add(a - b);
		ST.value++;
		return -1;
	}
	
	@Override
	public int mult() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add(a * b);
		ST.value++;
		return -1;
	}
	
	@Override
	public int div() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add(a / b);
		ST.value++;
		return -1;
	}
	
	@Override
	public int mod() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add(a % b);
		ST.value++;
		return -1;
	}
	
	@Override
	public int lt() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a < b) ? 1 : 0);
		ST.value++;		
		return -1;
	}
	
	@Override
	public int le() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a <= b) ? 1 : 0);
		ST.value++;
		return -1;
		}
	
	@Override
	public int gt() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a > b) ? 1 : 0);
		ST.value++;
		return -1;
		}
	
	@Override
	public int ge() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a >= b) ? 1 : 0);
		ST.value++;
		return -1;
	}
	
	@Override
	public int eq() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a == b) ? 1 : 0);
		ST.value++;
		return -1;
	}
	
	@Override
	public int ne() {
		int a = stack.get(ST.value - 1);
		int b = stack.get(ST.value - 2);
		stack.add((a != b) ? 1 : 0);
		ST.value++;
		return -1;
	}
	
	@Override
	public int to21() {return -1;}
	
	@Override
	public int to22() {return -1;}
	
	@Override
	public int new_() {return -1;}
	
	@Override
	public int free() {return -1;}
	
	@Override
	public int get() {
		return 4;
	}
	
	@Override
	public int put() {
		System.out.println("El tope de la pila es: " + stack.get(ST.value));
		return -1;
	}
}